﻿// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable CommentTypo
// ReSharper disable IdentifierTypo

/* CallNode.cs -- вызов свободной функции
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System;
using System.Collections.Generic;
using System.IO;

using AM.Kotik.Ast;
using AM.Text;

using Microsoft.Extensions.Logging;

#endregion

#nullable enable

namespace AM.Kotik.Barsik.Ast;

/// <summary>
/// Вызов свободной функции, например, `println`.
/// </summary>
internal sealed class CallNode
    : AtomNode
{
    #region Construction

    /// <summary>
    /// Конструктор.
    /// </summary>
    /// <param name="name">Имя функции.</param>
    /// <param name="arguments">Аргументы (если есть).</param>
    public CallNode
        (
            string name,
            IList<AtomNode> arguments
        )
    {
        Sure.NotNullNorEmpty (name);

        _name = name;
        _arguments = arguments;
    }

    #endregion

    #region Private members

    private readonly string _name;
    private readonly IList<AtomNode> _arguments;
    private FunctionDescriptor? _function;

    #endregion

    #region AtomNode members

    /// <inheritdoc cref="AtomNode.Compute"/>
    public override dynamic? Compute
        (
            Context context
        )
    {
        Sure.NotNull (context);

        var logger = Magna.Logger;

        // это может быть вызов лямбды
        if (context.TryGetVariable (_name, out var value))
        {
            if (value is LambdaNode lambda)
            {
                return lambda.Execute (context, _arguments);
            }

            logger.LogInformation ("Unexpected variable {Name}", _name);
            throw new BarsikException ($"Unexpected variable {_name}");
        }

        try
        {
            _function ??= context.ResolveFunction (_name).ThrowIfNull ($"Can't find function {_name}");
        }
        catch (Exception)
        {
            logger.LogInformation ("Can't find function {Name}", _name);
            throw;
        }

        var args = new List<dynamic?>();
        foreach (var node in _arguments)
        {
            var arg = _function.ComputeArguments
                ? node.Compute (context)
                : node;
            args.Add (arg);
        }

        dynamic? result;
        try
        {
            result = _function.CallPoint (context, args.ToArray());
        }
        catch (GotoException exception)
        {
            // не позволяем goto сбежать из функции
            logger.LogInformation ("Can't find label {Name}", exception.Label);
            throw new BarsikException ($"Can't find label {exception.Label}");
        }

        return result;
    }

    #endregion

    #region AstNode members

    /// <inheritdoc cref="AstNode.DumpHierarchyItem(string?,int,System.IO.TextWriter)"/>
    internal override void DumpHierarchyItem
        (
            string? name,
            int level,
            TextWriter writer
        )
    {
        base.DumpHierarchyItem (name, level, writer);

        DumpHierarchyItem ("Name", level + 1, writer, _name);
        foreach (var argument in _arguments)
        {
            argument.DumpHierarchyItem ("Arg", level + 1, writer);
        }
    }
    /// <inheritdoc cref="AstNode.GetNodeInfo"/>
    public override AstNodeInfo GetNodeInfo()
    {
        var result = new AstNodeInfo (this)
        {
            Name = "call",
            Description = _name
        };

        foreach (var argument in _arguments)
        {
            result.Children.Add (argument.GetNodeInfo());
        }

        return result;
    }

    #endregion

    #region Object members

    /// <inheritdoc cref="AstNode.ToString"/>
    public override string ToString()
    {
        var builder = StringBuilderPool.Shared.Get();
        builder.Append (_name);
        builder.Append ('(');
        builder.AppendJoin (',', _arguments);
        builder.Append (')');

        return builder.ReturnShared();
    }

    #endregion
}
